package query

import (
	"context"
	"gitee.com/bei_yi_ning/gorder/common/handler/redis"
	"gitee.com/bei_yi_ning/gorder/common/logging"
	"gitee.com/bei_yi_ning/gorder/stock/entity"
	"gitee.com/bei_yi_ning/gorder/stock/infra/integration"
	"github.com/pkg/errors"
	"sort"
	"strings"
	"time"

	"gitee.com/bei_yi_ning/gorder/common/decorator"
	domain "gitee.com/bei_yi_ning/gorder/stock/domain/stock"
	"github.com/sirupsen/logrus"
)

const (
	redisLockPrefix = "check_stock_"
)

type CheckIfItemsInStock struct {
	Items []*entity.ItemWithQuantity
}

type CheckIfItemsInStockHandler decorator.QueryHandler[CheckIfItemsInStock, []*entity.Item]

type checkIfItemsInStockHandler struct {
	stockRepo domain.Repository
	stripeAPI *integration.StripeAPI
}

func NewCheckIfItemsInStockHandler(
	stockRepo domain.Repository,
	stripeAPI *integration.StripeAPI,
	logger *logrus.Logger,
	metricClient decorator.MetricsClient,
) CheckIfItemsInStockHandler {
	if stockRepo == nil {
		logger.Fatalf("stockRepo is nil")
	}
	if stripeAPI == nil {
		logger.Fatalf("stripeAPI is nil")
	}
	return decorator.ApplyCommandDecorators[CheckIfItemsInStock, []*entity.Item](
		checkIfItemsInStockHandler{
			stockRepo: stockRepo,
			stripeAPI: stripeAPI,
		},
		logger,
		metricClient)
}

//var stub = map[string]string{
//	"1": "price_1RUdc6IvSjvMW3ZG7VZ4eMhi",
//	"2": "price_1RUqq6IvSjvMW3ZGhHR7vTYa",
//}

// 暂时原封不动返回
func (c checkIfItemsInStockHandler) Handle(ctx context.Context, query CheckIfItemsInStock) ([]*entity.Item, error) {
	if err := lock(ctx, getLockKey(query)); err != nil {
		return nil, errors.Wrapf(err, "redis lock error, key=%s", getLockKey(query))
	}
	defer func() {
		if err := unlock(ctx, getLockKey(query)); err != nil {
			logrus.Warnf("redis unlock fail, err=%v", err)
		}
	}()

	var res []*entity.Item
	var err error
	defer func() {
		f := logrus.Fields{
			"query": query,
			"res":   res,
		}
		if err != nil {
			logging.Errorf(ctx, f, "checkIfItemsInStock err=%v", err)
		} else {
			logging.Infof(ctx, f, "checkIfItemsInStock success , query=%v", query)
		}
	}()

	for _, item := range query.Items {
		priceID, err := c.stripeAPI.GetPriceByProductID(ctx, item.ID)
		if err != nil {
			logrus.Warnf("GetPriceByProductID error, itemID: %s , err=%v", item.ID, err)
			//continue
			return nil, err
		}
		res = append(res, &entity.Item{
			ID:       item.ID,
			Quantity: item.Quantity,
			PriceID:  priceID,
		})
	}
	// checkStock检查扣库存的合法性以及执行扣库存操作
	if err := c.checkStock(ctx, query.Items); err != nil {
		return nil, err
	}
	return res, nil
}

func getLockKey(query CheckIfItemsInStock) string {
	var ids []string
	for _, i := range query.Items {
		ids = append(ids, i.ID)
	}
	sort.Strings(ids) // 即便排了序我觉得key使用这个有点不太合理  回头再问问
	return redisLockPrefix + strings.Join(ids, ",")
}

func unlock(ctx context.Context, key string) error {
	return redis.Del(ctx, redis.LocalClient(), key)
}

func lock(ctx context.Context, key string) error {
	return redis.SetNX(ctx, redis.LocalClient(), key, "1", 5*time.Minute)
}

func (c checkIfItemsInStockHandler) checkStock(ctx context.Context, query []*entity.ItemWithQuantity) error {
	var ids []string
	for _, i := range query {
		ids = append(ids, i.ID)
	}
	records, err := c.stockRepo.GetStock(ctx, ids)
	if err != nil {
		return err
	}
	var idQuantityMap = make(map[string]int32)
	for _, r := range records {
		idQuantityMap[r.ID] += r.Quantity
	}
	var (
		ok     = true
		exceed []struct {
			ProductID string
			Want      int32
			Have      int32
		}
	)
	for _, i := range query {
		if i.Quantity > idQuantityMap[i.ID] {
			ok = false
			exceed = append(
				exceed,
				struct {
					ProductID string
					Want      int32
					Have      int32
				}{
					ProductID: i.ID,
					Want:      i.Quantity,
					Have:      idQuantityMap[i.ID],
				})
		}
	}
	if ok {
		return c.stockRepo.UpdateStock(ctx, query, func(
			ctx context.Context,
			existing []*entity.ItemWithQuantity,
			query []*entity.ItemWithQuantity,
		) ([]*entity.ItemWithQuantity, error) {
			var newItems []*entity.ItemWithQuantity
			existMap := make(map[string]int32)
			for _, e := range existing {
				existMap[e.ID] += e.Quantity
			}
			for _, item := range query {
				if _, ok := existMap[item.ID]; ok {
					existMap[item.ID] -= item.Quantity
				}
			}
			for productID, remainStock := range existMap {
				newItems = append(newItems, &entity.ItemWithQuantity{
					ID:       productID,
					Quantity: remainStock,
				})
			}
			//for _, e := range existing { // 可以用切片二重for  也可以map
			//	for _, q := range query {
			//		if e.ID == q.ID {
			//			newItems = append(newItems, &entity.ItemWithQuantity{
			//				ID:       e.ID,
			//				Quantity: e.Quantity - q.Quantity,
			//			})
			//		}
			//	}
			//}
			return newItems, nil
		})

	}
	return domain.ExceededStockError{exceed}
}
